#pragma once // 防止头文件被重复包含
#include <sys/socket.h>
#include <cstdlib>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string.h>
#include <memory>
#include <pthread.h>
#include "log.hpp"

#define BACKLOG 5 // 全连接队列最小值
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

class tcp_serve
{

public:
  static tcp_serve *git_tcp_object(int port) // 获取单例实例
  {
    if (tcp_ptr == nullptr)
    {
      pthread_mutex_lock(&mutex); // 加锁
      if (tcp_ptr == nullptr)
      {
        tcp_ptr = new tcp_serve(port);
        tcp_ptr->initserve();
      }
      pthread_mutex_unlock(&mutex); // 解锁
    }

    return tcp_ptr;
  }

  int git_socket_fd()
  {
    return _socket_fd;
  }

private:
  void initserve()
  {
    Socket();
    Bind();
    Listen();
  }

  tcp_serve(int port)
      : _port(port), _socket_fd(-1)
  {
  }

  ~tcp_serve()
  {
  }

  tcp_serve(const tcp_serve &x);
  const tcp_serve &operator=(const tcp_serve &x);

  void Socket()
  {
    int fd = socket(AF_INET, SOCK_STREAM, 0); // 用IPv4协议家族，TCP协议
    if (-1 == fd)
    {
      LOG(FATAL, "创建套接字失败");
      exit(1);
    } // 创建套接字失败

    _socket_fd = fd;
    LOG(INFO, "创建套接字成功");
  }

  void Bind()
  {
    struct sockaddr_in address;
    memset(&address, 0, sizeof(address)); // 把结构体初始化为0

    address.sin_family = AF_INET;         // IPv4协议家族
    address.sin_addr.s_addr = INADDR_ANY; // 服务器选择任意IP地址接收请求
    address.sin_port = htons(_port);      // 端口号信息,网络序列

    int opt = 1;
    if (setsockopt(_socket_fd, SOL_SOCKET, SO_REUSEADDR, (const void *)&opt, sizeof(opt)) < 0)
    {
      LOG(WARNING, "端口不能在短时间内连续绑定");
    } // 防止TIME_WAIT状态导致绑定端口失败
    LOG(INFO, "端口复用设置成功");
    if (-1 == bind(_socket_fd, (const struct sockaddr *)&address, (socklen_t)sizeof(address)))
    {
      LOG(FATAL, "绑定套接字失败");
      exit(2); // 绑定套接字失败
    }

    LOG(INFO, "套接字绑定成功");
  }

  void Listen()
  {
    if (-1 == listen(_socket_fd, BACKLOG))
    {

      LOG(FATAL, "声明自己可连接失败");
      exit(3);
    } // 请求链接失败

    LOG(INFO, "套接字允许监听链接");
  }

private:
  int _port;      // 端口
  int _socket_fd; // 套接字文件描述符
  static tcp_serve *tcp_ptr;
};

tcp_serve *tcp_serve::tcp_ptr = nullptr;
